# File: Engine.py
# Copyright 2010 
# Author: Andrew Dorrycott
# License: GNU Lesser General Public License
# Original Repository: http://code.google.com/p/bobthezombie/

import sys
import pygame
import random
import datetime
import math

from Standard import *
from NPCS import *
from Items import *
from Effects import *
from World import *
from Menu import *

class Engine():
    def __init__(self, width = 800, height = 600, screen = None):
        
        # Debug
        self.world_location_debug = False # This tells me where in the world the position is located
        self.show_world_boundaries_debug = False # Displays the red walls that keep you from walking through the world
        self.noclip = False # Allow Bob to walk the world!
        
        # The display to the game
        self.size = self.width, self.height = width, height
        self.screen = screen or pygame.display.set_mode(self.size)
        self.font = pygame.font.Font(pygame.font.get_default_font(), 20)
        self.music_volumn = 100
        self.sound_volumn = 100
        
        # Game Controls
        self.control_move_west = pygame.K_a
        self.control_move_east = pygame.K_d
        self.control_move_north = pygame.K_w
        self.control_move_south = pygame.K_s
        self.control_shoot = pygame.K_SPACE
        self.control_tech_tree = pygame.K_t
        self.control_use = pygame.K_e
        
        # Build all other class objects that depend on a fully operation Engine object last
        
        # Menu that displays on top of the game when you start the game and open it up from in game
        self.menu = Menu(self)
        
        # Open up the main menu
        self.MainMenu()
        
        # Rebuild general game
        self.BuildGame()
    
    def BuildGame(self, new_game = True):
        # Container for all npcs and zombies in the world
        self.npcs = []
        
        # Flying effects
        self.effects = []
        
        # All items in the game
        self.items = []
        
        # What is the user holding onto
        self.keys_held = {}
        
        # Create Bob
        self.bob = Bob(self.width / 2.0, self.height / 2.0, self)
        
        # Build World
        #self.world = World(self.width / 2.0, self.height / 2.0, self)
        self.world = World(1400, 1550, self)
        
        # This information is for new games only
        if new_game:
            # Right here we need to display the back story and an explanation of what to do :D
            self.ShowIntro()
            
            
            # Add items
            # Should only be 10 Syringes in the entire game
            self.items.append( Syringe(72, 111, self) )
            self.items.append( Syringe(68, 338, self) )
            self.items.append( Syringe(632, 694, self) )
            self.items.append( Syringe(765, 680, self) )
            self.items.append( Syringe(789, 294, self) )
            self.items.append( Syringe(705, 104, self) )
            self.items.append( Syringe(506, 104, self) )
            self.items.append( Syringe(322, 101, self) )
            
            # cheating for now
            self.items.append( Syringe(300, 300, self) )
            self.items.append( Syringe(300, 300, self) )
            self.items.append( Syringe(300, 300, self) )
            self.items.append( Syringe(300, 300, self) )
            self.items.append( Syringe(300, 300, self) )
            self.items.append( Syringe(300, 300, self) )
            self.items.append( Syringe(300, 300, self) )
            self.items.append( Syringe(300, 300, self) )
            self.items.append( Syringe(300, 300, self) )
            self.items.append( Syringe(300, 300, self) )
            self.items.append( Syringe(300, 300, self) )
            
            # Some food to heal Bob
            self.items.append( Meat(976, 584, self) )
            self.items.append( Meat(500, 300, self) )
            self.items.append( Meat(500, 300, self) )
            self.items.append( Meat(500, 300, self) )
            
            # Some Documents to give Bob upgrades!
            self.items.append( ResearchDocument(977, 488, self) )
            
            # Add NPCs
            self.npcs.append( Intern(400, 200, self, current_weapon = 'Hands') )
            self.npcs.append( Male(60, 100, self, current_weapon = 'Hands') )
            self.npcs.append( Female(201, 462, self, current_weapon = 'Shotgun') )
            self.npcs.append( Male(660, 450, self, current_weapon = 'Hands') )
            self.npcs.append( Female(845, 623, self, current_weapon = 'Hands') )
            self.npcs.append( Male(823, 414, self, current_weapon = 'Shotgun') )
            self.npcs.append( Female(904, 213, self, current_weapon = 'Pistol') )
            self.npcs.append( Male(335, 637, self, current_weapon = 'Rifle') )
            self.npcs.append( Female(14, 542, self, current_weapon = 'Hands') )
        
            # Begin the game!
            self.Run()
    
    def ShowIntro(self):
        alive = True
        story = ["  Everyone talks of the first day of infection being the zombie apocalypse.",
                 "Though the problem with that is the infection never spread. I sometimes ",
                 "wonder if I was cursed or blessed with this single, most world bending event.",
                 "I am Bob, the Zombie, not just a zombie, but one in the world of all humans.",
                 "I used to work for a firm in San Antonio as an accountant. I had a family,",
                 "beautiful wife and two lovely, bright children. One day one of the interns at",
                 "the firm decided to fool around and play with a cart left unattended in the",
                 "hallway stacked full with unlabeled chemicals. He mixed a whole lot of the",
                 "chemicals which created a concoction that looked bright and clear. He",
                 "decided to pour it into a cup and pass it off as water to cover up his",
                 "fooling around. This intern worked for me and brought back this 'water' for",
                 "me to enjoy.",
                 "",
                 "  Long story short I drank this 'water' and felt effects shortly afterwards.",
                 "I ran to the bathroom feeling like I was going to vomit. Once on the toilet I",
                 "blacked out. My journey begins when I died on that lonely toilet. I am no",
                 "longer Bob the accountant at the firm, I am now Bob the Zombie. Now mind",
                 "you, I am not some ordinary zombie you see in our popular culture, I can",
                 "think, feel, act and remember who I once was. But I feel hungry, something",
                 "drives me to eat meat, I feel and look pale, the mirror in the room shows",
                 "this sickish man starring back. I have got to get out of here but I cannot",
                 "just go back to work like this it will stir up the entire company.",
                 ]
        gap = 22
        
        self.screen.fill( (0, 0, 0) ) # Fill the background with black
        
        center_y = 25
        
        for line in story:
            text = self.font.render( line, 0, (255, 255, 255) )
            
            center_x = 25
            
            self.screen.blit( text, ( center_x, center_y ) )
            
            center_y += gap # only adjust the next y after the rendering
        
        self.screen.blit( self.font.render( "Press any key to skip...", 0, (180, 180, 180) ), ( 550, 560 ) )
        pygame.display.flip() # Perform the first and hopefully only flip needed for now
        
        while alive:
            
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    self.Quit()
                
                if ( event.type == pygame.KEYDOWN ):
                    # Press any key to continue
                    alive = False
        
        self.HowToPlay() # Give the player a chance to know what their controls are bound to
    
    def HowToPlay(self):
        alive = True
        story = ["          How to play",
                 "",
                 "Movement Keys",
                 "  '%s' to go up" % pygame.key.name(self.control_move_north).capitalize(),
                 "  '%s' to go down" % pygame.key.name(self.control_move_south).capitalize(),
                 "  '%s' to go left" % pygame.key.name(self.control_move_west).capitalize(),
                 "  '%s' to go right" % pygame.key.name(self.control_move_east).capitalize(),
                 "",
                 "Interaction Keys",
                 "  '%s' to collect items" % pygame.key.name(self.control_use).capitalize(),
                 "  '%s' to fire current weapon" % pygame.key.name(self.control_shoot).capitalize(),
                 "  '%s' to open tech tree and spend DNA on upgrades" % pygame.key.name(self.control_tech_tree).capitalize(),
                 "  'ESC' to open the menu",
                 "",
                 "How to Survive",
                 "  Collect Weapons      and ammo      to use on the humans",
                 "  Collect meat      to heal yourself and gain sanity back",
                 "",
                 "How to Win",
                 "  Collect 10 Syringes      to cure Bob",
                 "  Kill or Convert all humans on to Zombies!",
                 "",
                 "After killing or converting a human Bob loses some sanity",
                 "",
                 "Good Bob at Sanity: 100 looks like",
                 "",
                 "",
                 "Good Bob at Sanity: -100 looks like",
                 ]
        gap = 20
        
        self.screen.fill( (0, 0, 0) ) # Fill the background with black
        
        center_y = 15
        
        for line in story:
            text = self.font.render( line, 0, (255, 255, 255) )
            
            center_x = 25
            
            self.screen.blit( text, ( center_x, center_y ) )
            
            center_y += gap # only adjust the next y after the rendering
        
        self.screen.blit( self.font.render( "Press any key to skip...", 0, (180, 180, 180) ), ( 550, 560 ) )
        
        scalor = 2.0
        evil_bob = pygame.image.load( IMAGES_BOB["EvilFaceSouth"] )
        evil_bob = self.image = pygame.transform.scale(evil_bob, (int( evil_bob.get_size()[0] * scalor ), int( evil_bob.get_size()[1] * scalor ) ))
        
        good_bob = pygame.image.load( IMAGES_BOB["FaceSouth"] )
        good_bob = self.image = pygame.transform.scale(good_bob, (int( good_bob.get_size()[0] * scalor ), int( good_bob.get_size()[1] * scalor ) ))
        
        syringe = pygame.image.load( IMAGES_SYRINGE[1] )
        #syringe = self.image = pygame.transform.scale(syringe, (int( syringe.get_size()[0] * scalor ), int( syringe.get_size()[1] * scalor ) ))
        
        meat = pygame.image.load( IMAGES_MEAT[1] )
        #meat = self.image = pygame.transform.scale(meat, (int( meat.get_size()[0] * scalor ), int( meat.get_size()[1] * scalor ) ))
        
        weapon = pygame.image.load( IMAGES_SHOTGUN )
        weapon = self.image = pygame.transform.scale(weapon, (int( weapon.get_size()[0] * scalor ), int( weapon.get_size()[1] * scalor ) ))
        
        ammo = pygame.image.load( IMAGES_SHOTGUN_AMMO )
        
        self.screen.blit( good_bob, ( 370, 475 ) )
        self.screen.blit( evil_bob, ( 370, 535 ) )
        self.screen.blit( syringe, ( 235, 395 ) )
        self.screen.blit( meat, (170, 335) )
        self.screen.blit( weapon, (205, 310) )
        self.screen.blit( ammo, (350, 317) )
        
        pygame.display.flip() # Perform the first and hopefully only flip needed for now
        
        while alive:
            
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    self.Quit()
                
                if ( event.type == pygame.KEYDOWN ):
                    # Press any key to continue
                    alive = False
        
        return
    
    def ShowCredits(self):
        # Fade the game to black screen to ease transition to credits
        blank_image = Effect(0, 0, self, IMAGES_BLACK_OUT, IMAGES_BLACK_OUT, scale = 50.0) # I want to cover the screen and then some
        blank_image.view_image.set_alpha(0)
        
        fade_length = 0
        
        while fade_length < 45:
            self.Redraw(False) # Draw the game in the background
            blank_image.view_image.set_alpha(255 * (fade_length / 45.0) ) # Fade in
            self.screen.blit(blank_image.view_image, blank_image.view_rectangle) # Render the fader
            pygame.display.flip() # Bring the new image to the front
            fade_length += 1
        
        alive = True
        story = ["Bob the Zombie Credits",
                 "",
                 "Lead Programmer",
                 "  Andrew Dorrycott",
                 "",
                 "Lead Art Designer",
                 "  Rebecca Dorrycott",
                 "",
                 "Special thanks to",
                 "  Alyson Barr",
                 "  Alan Erwin",
                 "  Marisela Narvaez",
                 "",
                 "",
                 "Copyright 2010",
                 "",
                 "Thank you For playing!",
                 "",
                 "Email Feedback to kusinwolf@gmail.com",
                 ]
        gap = 22
        
        self.screen.fill( (0, 0, 0) ) # Fill the background with black
        
        center_y = ( self.height / 2.0 ) - ( ( len( story ) / 2.0 + 1) * gap )
        
        for line in story:
            text = self.font.render( line, 0, (255, 255, 255) )
            
            center_x = 200
            
            self.screen.blit( text, ( center_x, center_y ) )
            
            center_y += gap # only adjust the next y after the rendering
        
        self.screen.blit( self.font.render( "Press any key to skip...", 0, (180, 180, 180) ), ( 550, 560 ) )
        pygame.display.flip() # Perform the first and hopefully only flip needed for now
        
        while alive:
            
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    self.Quit()
                
                if ( event.type == pygame.KEYDOWN ):
                    # Press any key to continue
                    alive = False
        
        return
    
    def ShowThanks(self):
        alive = True
        story = ["Thank you for playing",
                 "Bob the Zombie!",
                 "Have a great day!",
                 ]
        gap = 25
        
        self.screen.fill( (0, 0, 0) ) # Fill the background with black
        
        center_y = ( self.height / 2.0 ) - ( ( len( story ) / 2.0 + 1) * gap )
        
        for line in story:
            text = self.font.render( line, 0, (255, 255, 255) )
            
            center_x = ( self.width / 2.0 ) - ( text.get_size()[0] / 2.0 )
            
            self.screen.blit( text, ( center_x, center_y ) )
            
            center_y += gap # only adjust the next y after the rendering
        
        self.screen.blit( self.font.render( "Press any key to skip...", 0, (180, 180, 180) ), ( 550, 560 ) )
        pygame.display.flip() # Perform the first and hopefully only flip needed for now
        
        while alive:
            
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    self.Quit()
                
                if ( event.type == pygame.KEYDOWN ):
                    # Press any key to continue
                    alive = False
        
        return
    
    def SendEvent(self, type, **kws):
        # Create new event with specific value
        pygame.event.post( pygame.event.Event(type, kws) )
    
    def HUD(self):
        health = self.bob.health
        ammo = self.bob.current_ammo[ self.bob.current_weapon ]
        dna = self.bob.dna
        cure = self.bob.cure
        sanity = self.bob.sanity - 100 # Shifts scale from 0 - 200 to -100 - 100
        shadow_shift = 2 # How far to shift each shadow
        
        DNA_bar_empty = pygame.image.load(IMAGES_DNA_BAR_EMPTY)
        DNA_bar_full = pygame.image.load(IMAGES_DNA_BAR_FULL)
        
        DNA_bar_empty = pygame.transform.scale(DNA_bar_empty, (int( DNA_bar_empty.get_size()[0] * ( self.width / 128.0 ) ), int( DNA_bar_empty.get_size()[1] * 0.25 ) ))
        DNA_bar_full = pygame.transform.scale(DNA_bar_full, (int( DNA_bar_full.get_size()[0] * ( self.width / 128.0 ) ), int( DNA_bar_full.get_size()[1] * 0.25 ) ))
        
        DNA_bar_full_rect = DNA_bar_full.get_rect()
        DNA_bar_empty_rect = DNA_bar_empty.get_rect()
        
        shift_x = ( self.width * ( cure / 100.0 ) )
        DNA_bar_empty_rect = DNA_bar_empty_rect.move([ shift_x, 0])
        
        name = "Hands"
        if self.bob.current_weapon == 'Pistol':
            name = "Pistol Ammo"
        elif self.bob.current_weapon == 'Rifle':
            name = "Rifle Ammo"
        elif self.bob.current_weapon == 'Shotgun':
            name = "Shotgun Ammo"
        
        self.screen.blit( DNA_bar_full, DNA_bar_full_rect )
        self.screen.blit( DNA_bar_empty, DNA_bar_empty_rect )
        
        self.screen.blit( self.font.render("Health: %d" % health, 0, (0, 0, 0)), ( 10 + shadow_shift, self.height - 60 + shadow_shift ) ) # Black Background
        self.screen.blit( self.font.render("Health: %d" % health, 0, (255, 255, 255)), ( 10, self.height - 60 ) )
        
        if name == "Hands":
            self.screen.blit( self.font.render(name, 0, (0, 0, 0)), ( 10 + shadow_shift, self.height - 30 + shadow_shift) ) # Black Background
            self.screen.blit( self.font.render(name, 0, (255, 255, 255)), ( 10, self.height - 30 ) )
        else:
            self.screen.blit( self.font.render("%s: %d" % (name, ammo), 0, (0, 0, 0)), ( 10 + shadow_shift, self.height - 30 + shadow_shift ) ) # Black Background
            self.screen.blit( self.font.render("%s: %d" % (name, ammo), 0, (255, 255, 255)), ( 10, self.height - 30 ) )
        
        self.screen.blit( self.font.render("Sanity: %d" % sanity, 0, (0, 0, 0)), ( self.width - 150 + shadow_shift, self.height - 60 + shadow_shift ) ) # Black Background
        self.screen.blit( self.font.render("Sanity: %d" % sanity, 0, (255, 255, 255)), ( self.width - 150, self.height - 60 ) )
        
        self.screen.blit( self.font.render("DNA: %d" % dna, 0, (0, 0, 0)), ( self.width - 150 + shadow_shift, self.height - 30 + shadow_shift ) ) # Black Background
        self.screen.blit( self.font.render("DNA: %d" % dna, 0, (255, 255, 255)), ( self.width - 150, self.height - 30 ) )
        
        cure_text = "%d%% Cured" % (cure / 100.0 * 100)
        cure_render_shadow = self.font.render(cure_text, 0, (0, 0, 0))
        cure_render = self.font.render(cure_text, 0, (255, 255, 255))
        self.screen.blit( cure_render_shadow, ( (self.width / 2.0 + shadow_shift) - (cure_render_shadow.get_size()[0] / 2.0), 5 + shadow_shift ) ) # Black background
        self.screen.blit( cure_render, ( (self.width / 2.0) - (cure_render.get_size()[0] / 2.0), 5 ) )
    
    def MainMenu(self):
        self.menu.Run('main_menu')
    
    def TechTree(self):
        self.menu.Run('in_game_tech_tree', entered_tech_tree = True)
    
    def Menu(self):
        # Allow the menu to run for now
        self.menu.Run('in_game_menu')
    
    def VictoryCuredMenu(self):
        self.menu.Run('in_game_victory_cure')
    
    def VictoryZombieApocalypseMenu(self):
        self.menu.Run('in_game_victory_zombie_apocalypse')
    
    def VictoryRampageMenu(self):
        self.menu.Run('in_game_victory_rampage')
    
    def GameOverMenu(self):
        self.menu.Run('in_game_game_over')
    
    def RemoveNPC(self, npc):
        weapon = npc.current_weapon
        
        # Drop the weapon they have
        if weapon != 'Hands':
            if weapon == "Rifle":
                self.items.append( Rifle(npc.current_x, npc.current_y, self) )
            elif weapon == "Shotgun":
                self.items.append( Shotgun(npc.current_x, npc.current_y, self) )
            elif weapon == "Pistol":
                self.items.append( Pistol(npc.current_x, npc.current_y, self) )
        
        try:
            npc = self.npcs.pop( self.npcs.index(npc) ) # Get the NPC and remove it
        
            del npc # clear the memory of it
        except ValueError:
            pass # NPC did not exist, must've died on the field some otherway
        
        if self.npcs == []:
            self.SendEvent(CustomEvents.rampage)
        
        zombie_apocalypse = True
        for npc in self.npcs:
            if not npc.is_zombie:
                zombie_apocalypse = False
        
        if zombie_apocalypse:
            self.SendEvent(CustomEvents.zombie_apocalypse)
        
        return
    
    def RemoveEffect(self, effect):
        try:
            effect = self.effects.pop( self.effects.index(effect) ) # Get the Bullet and remove it
            
            del effect # clear the memory of it
        except ValueError:
            pass # Bullet did not exist, must've died on the field some otherway
        
        return 
    
    def RemoveItem(self, item):
        try:
            item = self.items.pop( self.items.index(item) )
            
            del item
        except ValueError:
            pass # Item did not exist, must've died on the field some otherway
        
        return
    
    def Run(self):
        while True:
            
            # Generate a new random seed
            random.seed(datetime.datetime.now()) # In hopes that the AI move more randomly
            
            self.Redraw()
            
            # Handle any held keys before a new event is brought in
            if self.keys_held:
                self.handleKeysHeld()
            
            self.StopMovingNPCsAnimation()
            
            self.GenerateGUIParticals()
            
            self.MoveBullets()
            self.AgeEffects()
            self.MoveGUIParticals()
            
            for npc in self.npcs:
                npc.AI()
            
            # Take care of all the events generated
            self.OnEvent( pygame.event.get() )
    
    def SaveGame(self, slot):
        game_save = open("saves/save%d.txt" % slot, "w")
        
        game_save.write("""%(class)s, %(x)s, %(y)s, %(current_weapon)s, %(health)s, %(sanity)s, %(current_outfit)s, %(cure)s, %(Stealth)s, %(Regeneration)s, %(Human Killing Machine)s, %(Zombie Army)s, %(Research Knowledge)s, %(Pistol)s, %(Rifle)s, %(Shotgun)s, %(Pistol_ammo)s, %(Rifle_ammo)s, %(Shotgun_ammo)s\n"""% \
                        {"class": self.bob.__class__.__name__, # It's Bob :D
                         "x": self.bob.world_x, # This is the world_x for moving the world around specifically to regenerate the world
                         "y": self.bob.world_y, # This is the world_y for moving the world around specifically to regenerate the world
                         "cure": self.bob.cure,
                         "current_weapon": self.bob.current_weapon,
                         "health": self.bob.health,
                         "sanity": self.bob.sanity,
                         "current_outfit": self.bob.current_outfit,
                         "Stealth": self.bob.tech_levels["Stealth"],
                         "Regeneration": self.bob.tech_levels["Regeneration"],
                         "Human Killing Machine": self.bob.tech_levels["Human Killing Machine"],
                         "Zombie Army": self.bob.tech_levels["Zombie Army"],
                         "Research Knowledge": self.bob.tech_levels["Research Knowledge"],
                         "Pistol": "Pistol" in self.bob.has_weapons, # True/False
                         "Rifle": "Rifle" in self.bob.has_weapons, # True/False
                         "Shotgun": "Shotgun" in self.bob.has_weapons, # True/False
                         "Pistol_ammo": self.bob.current_ammo["Pistol"],
                         "Rifle_ammo": self.bob.current_ammo["Rifle"],
                         "Shotgun_ammo": self.bob.current_ammo["Shotgun"],
                         })
        
        for npc in self.npcs:
            game_save.write("%(class)s, %(x)s, %(y)s, %(is_infected)s, %(current_weapon)s, %(health)s\n" % \
                            {"class": npc.__class__.__name__, # I want to know which class was used
                             "x": npc.current_x, # This is also world_x for all NPCs
                             "y": npc.current_y, # This is also world_y for all NPCs
                             "is_infected": npc.is_infected,
                             "current_weapon": npc.current_weapon,
                             "health": npc.health,})
        
        for item in self.items:
            game_save.write("%(class)s, %(x)s, %(y)s\n" % \
                            {"class": item.__class__.__name__, # I want to know which class was used
                             "x": item.current_x, # This is also world_x for all NPCs
                             "y": item.current_y, # This is also world_y for all NPCs
                             })
        
        game_save.close()
        
    
    def LoadGame(self, slot):
        # Open the file, grab everything and close it up
        game_save = open("saves/save%d.txt" % slot, "r")
        game_save_lines = game_save.readlines()
        game_save.close()
        
        # Build the basic requirement first before continuing
        self.BuildGame(new_game = False) # This is not a new game :3
        
        for line in game_save_lines:
            line = line.split(",")
            if line[0] == "Bob":
                # Grab all the necessary values
                name, x, y, current_weapon, health, sanity, current_outfit, cure, stealth, regeneration, hkm, zombiearmy, researchknowledge, pistol, rifle, shotgun, pistol_ammo, rifle_ammo, shotgun_ammo = line
                
                # How far should we shift the world from where Bob moved
                shift_world_by_x = float(x) - self.bob.current_x
                shift_world_by_y = float(y) - self.bob.current_y
                
                # This will move the world the proper direction
                self.world.MoveEast(shift_world_by_x)
                self.world.MoveSouth(shift_world_by_y)
                
                # Manually set these values
                self.bob.world_x = float(x) # Reassign the world_x coordinate
                self.bob.world_y = float(y) # Reassign the world_y coordinate
                self.bob.current_weapon = current_weapon.strip(" ")
                self.bob.health = float(health)
                self.bob.sanity = int(sanity)
                self.bob.current_outfit = current_outfit.strip(" ")
                self.bob.cure = int(cure)
                
                ######
                # These values have to be updated by the logic of the NPC class, otherwise it could result in devistation
                ######
                
                # Tech info
                self.bob.UpdateTech("Stealth", int(stealth) )
                self.bob.UpdateTech("Regeneration", int(regeneration) )
                self.bob.UpdateTech("Human Killing Machine", int(hkm) )
                self.bob.UpdateTech("Zombie Army", int(zombiearmy) )
                self.bob.UpdateTech("Research Knowledge", int(researchknowledge) )
                
                # Add the Weapons to Bob
                self.bob.AddWeapon(pistol.strip(" "))
                self.bob.AddWeapon(rifle.strip(" "))
                self.bob.AddWeapon(shotgun.strip(" "))
                
                # Ammo amounts
                self.bob.current_ammo["Pistol"] = int(pistol_ammo)
                self.bob.current_ammo["Rifle"] = int(rifle_ammo)
                self.bob.current_ammo["Shotgun"] = int(shotgun_ammo)
                
            elif line[0] in ["Intern", "Female", "Male", "ZombieMale", "ZombieFemale", "NPC"]:
                # Grab all the variables
                name, x, y, is_infected, current_weapon, health = line
                # default class
                ClassObject = NPC
                
                # Get the right Class to use
                if name == "Intern":
                    ClassObject = Intern
                elif name == "Female":
                    ClassObject = Female
                elif name == "Male":
                    ClassObject = Male
                elif name == "ZombieFemale":
                    ClassObject = ZombieFemale
                elif name == "ZombieMale":
                    ClassObject = ZombieMale
                elif name == "ZombieIntern":
                    ClassObject = ZombieInter
                
                # Build the npc and update its values
                new_npc = ClassObject( float(x), float(y), self )
                new_npc.health = float(health)
                new_npc.is_infected = bool(is_infected)
                
                # Add the new npc to the list
                self.npcs.append( new_npc )
            
            elif line[0] in ["ResearchDocument", "DNA", "DisguiseLabCoat", "ShotgunAmmo", "RifleAmmo",
                             "PistolAmmo", "Shotgun", "Rifle", "Pistol", "Syringe", "Meat"]:
                # Grab all the variables
                name, x, y = line
                # default class
                ClassObject = Item
                
                # Get the right Class to use
                if name == "ResearchDocument":
                    ClassObject = ResearchDocument
                elif name == "DNA":
                    ClassObject = DNA
                elif name == "DisguiseLabCoat":
                    ClassObject = DisguiseLabCoat
                elif name == "ShotgunAmmo":
                    ClassObject = ShotgunAmmo
                elif name == "RifleAmmo":
                    ClassObject = RifleAmmo
                elif name == "PistolAmmo":
                    ClassObject = PistolAmmo
                elif name == "Shotgun":
                    ClassObject = Shotgun
                elif name == "Rifle":
                    ClassObject = Rifle
                elif name == "Pistol":
                    ClassObject = Pistol
                elif name == "Syringe":
                    ClassObject = Syringe
                elif name == "Meat":
                    ClassObject = Meat
                
                # Build the item and update its values
                new_item = ClassObject( float(x), float(y), self )
                
                # Add the new item to the list
                self.items.append( new_item )
        
        if self.bob == None:
            return # This load is broke, do not continue with Bob!
        
        # Begin the game!
        self.Run()
    
    def GenerateGUIParticals(self):
        if random.random() < 0.03:
            self.effects.append( DNAPartical(0, 32 * random.random(), self) )
    
    def MoveGUIParticals(self):
        for effect in self.effects:
            if not isinstance(effect, DNAPartical):
                continue # Skip this effect as it is not the DNA Partical
            
            effect.Move()
    
    def ChangeKey(self, key, draw_game = False):
        new_key = None
        
        if draw_game:
            # Redraw the back screen
            self.Redraw(flip = False)
        else:
            self.screen.fill( (0,0,0) )
        
        # Tell the user that they're reassigning a key
        self.screen.blit(self.menu.menu_background_border, self.menu.menu_background_border_rect)
        self.screen.blit(self.menu.menu_background_inner, self.menu.menu_background_inner_rect)
        
        text = self.font.render("Reassigning %s" % key, 0, (0, 255, 100) )
        text2 = self.font.render("Press any key...", 0, (0, 255, 100) )
        text3 = self.font.render("Esc or Enter to cancel", 0, (0, 255, 100) )
        
        center_y = ( self.height / 2.0 )
        
        self.screen.blit( text, ( ( self.width / 2.0 ) - ( text.get_size()[0] / 2.0 ), center_y ) )
        self.screen.blit( text2, ( ( self.width / 2.0 ) - ( text2.get_size()[0] / 2.0 ), center_y + 20 ) )
        self.screen.blit( text3, ( ( self.width / 2.0 ) - ( text3.get_size()[0] / 2.0 ), center_y + 40 ) )
        
        pygame.display.flip()
        
        # listen for the new key
        while not new_key:
            for event in pygame.event.get():
                if event.type == pygame.KEYDOWN:
                    new_key = event.key
        
        # Cancel in case of esc or enter is pressed
        if new_key and new_key != pygame.K_ESCAPE and new_key != pygame.K_RETURN:
            if key == 'Up':
                self.control_move_north = new_key
                
            elif key == 'Down':
                self.control_move_south = new_key
                
            elif key == 'Left':
                self.control_move_west = new_key
                
            elif key == 'Right':
                self.control_move_east = new_key
                
            elif key == 'Fire':
                self.control_shoot = new_key
                
            elif key == 'Use/Pickup':
                self.control_use = new_key
                
            elif key == 'Open Tech Tree':
                self.control_tech_tree = new_key
        
        del new_key # Clean up memory
    
    def ToggleMusic(self):
        if self.music_volumn:
            self.music_volumn = 0
            
            return
        
        self.music_volumn = 100
    
    def ToggleSound(self):
        if self.sound_volumn:
            self.sound_volumn = 0
            
            return
        
        self.sound_volumn = 100
    
    def Quit(self):
        self.ShowThanks()
        # Close the game
        pygame.quit()
        sys.exit()
    
    def Redraw(self, flip = True):
        self.screen.fill( (0, 0, 0) ) # Fill the background with black
        
        # Order is important (Bottom -> Up build) :)
        self.world.Redraw(self.screen)
        
        for item in self.items:
            item.Redraw(self.screen)
        
        # Always render Bob above items
        self.bob.Redraw(self.screen)
        
        for npc in self.npcs:
            npc.Redraw(self.screen)
        
        for effect in self.effects:
            if isinstance(effect, DNAPartical):
                continue # Skip particals
            
            effect.Redraw(self.screen)
        
        self.HUD()
        
        for effect in self.effects:
            if isinstance(effect, DNAPartical):
                effect.Redraw(self.screen) # Redraw the particals above the bar
        
        if flip:
            pygame.display.flip()
    
    def MoveBullets(self):
        # Move the bullets, check if they run into Bob or the world
        for effect in self.effects:
            if not isinstance(effect, Bullet): # If not a bullet effect
                continue # Skip this object and start the loop again
            
            effect.MoveBullet()
            
            if effect.WillCollideWithBoundariesAt():
                # If we hit Bob then hurt him
                if pygame.sprite.collide_mask(self.bob, effect):
                    self.bob.UpdateHealth(effect.damage)
                    
                else: # Otherwise hurt the NPC
                    npc = self.GetCollidingNPC(effect)
                    
                    if npc:
                        effect.CauseDamage(npc)
                
                self.RemoveEffect(effect) # We hit something so clean up time
    
    def AgeEffects(self):
        for effect in self.effects:
            effect.UpdateAge(1)
            if effect.GetCurrentAge() >= effect.GetAgeLimit():
                self.RemoveEffect(effect)
    
    def UpgradeSkill(self, skill):
        self.bob.UpdateTech(skill, 1)
    
    def StopMovingNPCsAnimation(self):
        for npc in self.npcs:
            pass
        
        self.bob.StopMovingAnimation()
    
    def GetCollidingNPC(self, collider):
        """ Find which npc the collider is running into """
        
        colliding_npc = None
        
        for npc in self.npcs:
            if pygame.sprite.collide_mask(collider, npc) and npc != collider:
                colliding_npc = npc
                break
        
        return colliding_npc
    
    def IsCollidingWithOtherNPCs(self, incoming_npc):
        """ Find out if the moving NPC is colliding with another NPC """
        return self.GetCollidingNPC(incoming_npc) # It's simplier to find out which NPC we ran into if any at all than to check twice
    
    def GetCollidingWithItem(self):
        """ Find which item Bob is colliding with """
        
        colliding_item = None
        
        for item in self.items:
            # Bob has a rect collision while the item will have a circle collision
            if pygame.sprite.collide_circle(self.bob, item):
                colliding_item = item
        
        return colliding_item
    
    def handleKeysHeld(self):
        for key in self.keys_held:
            self.handleKeyPressedEvents( key )
    
    def OnEvent(self, events):
        for event in events:
            if event.type == pygame.QUIT:
                self.Quit()
            
            self.HandleEvent(event)
    
    def HandleEvent(self, event):
        if event.type in []:
            return # Ignore these events for now
        
        pressed = pygame.mouse.get_pressed()
        
        if ( event.type == pygame.MOUSEBUTTONDOWN ) and ( pressed == (1, 0, 0) ):
            # Button 1
            
            return # Do not continue

        if ( event.type == pygame.MOUSEBUTTONDOWN ) and ( pressed == (0, 0, 1) ):
            # Button 2
            
            return # Do not continue
        
        if ( event.type == pygame.KEYDOWN ):
            self.handleKeyPressedEvents( event.key )
            
            return # Do not continue
        
        if ( event.type == pygame.KEYUP ):
            self.handleKeyReleasedEvents( event.key )
            
            return # Do not continue
        
        if ( event.type == CustomEvents.new_game ):
            self.BuildGame()
            
            return
        
        if ( event.type == CustomEvents.died ):
            if event.object.is_bob == True:
                self.GameOverMenu()
            else:
                self.bob.UpdateSanity(-20)
                self.RemoveNPC( event.object )
            
            return
        
        if ( event.type == CustomEvents.quit_game ):
            self.MainMenu()
            
            return 
        
        if ( event.type == CustomEvents.cured ):
            # Victory with Bob becoming Human again
            self.ShowCredits() # Display the Credits
            self.VictoryCuredMenu() # Then the victory menu
            
            return
        
        if ( event.type == CustomEvents.zombie_apocalypse ):
            # Victory with Bob converting everyone to zombies
            self.ShowCredits() # Display the Credits
            self.VictoryZombieApocalypseMenu() # Then the victory menu
            
            return
        
        if ( event.type == CustomEvents.rampage ):
            # Victory with Bob killing the entire town
            self.ShowCredits() # Display the Credits
            self.VictoryRampageMenu() # Then the victory menu
            
            return
        
        if ( event.type == CustomEvents.converted ):
            
            self.bob.UpdateSanity(-10) # Remove some Sanity from Bob because he's spreading the infection
            
            # Remove the NPC from the NPC's list
            self.RemoveNPC(event.object)
            
            x = event.object.current_x
            y = event.object.current_y
            
            if isinstance(event.object, Female):
                image_collection = IMAGES_ZOMBIE_FEMALE
                ZombieClass = ZombieFemale # Class to call for creating the new zombie
            elif isinstance(event.object, Male):
                image_collection = IMAGES_ZOMBIE_MALE
                ZombieClass = ZombieMale # Class to call for creating the new zombie
            elif isinstance(event.object, Intern):
                image_collection = IMAGES_ZOMBIE_INTERN
                ZombieClass = ZombieIntern # Class to call for creating the new zombie
            else: # In case of failure use a default
                image_collection = IMAGES_ZOMBIE_MALE
                ZombieClass = ZombieMale
            
            ZombieNPC = ZombieClass(x, y, self,
                                    view_image = image_collection['FaceSouth'],
                                    collision_image = image_collection['CollisionMask'],
                                    image_collection = image_collection,
                                    scale = event.object.scale,
                                    current_weapon = 'Hands')
            
            self.npcs.append( ZombieNPC )
            
            del ZombieNPC # Clean up the memory
            
            return
        
        if ( event.type == CustomEvents.picked_up_item ):
            self.effects.append( Pop( event.current_x, event.current_y, self, found_text = event.found_text) )
            
            return
    
    def handleKeyReleasedEvents( self, key ):
        
        if key == self.control_shoot:
            self.bob.has_fired = False
            
            return
        
        if key == self.control_move_south:
            if self.keys_held.pop( key, None ):
                self.bob.StopMovingSouth( )
            
            return  # Do not continue
        
        if key == self.control_move_north:
            if self.keys_held.pop( key, None ):
                self.bob.StopMovingNorth( )
            
            return  # Do not continue
        
        if key == self.control_move_west:
            if self.keys_held.pop( key, None ):
                self.bob.StopMovingWest( )
            
            return  # Do not continue
        
        if key == self.control_move_east:
            if self.keys_held.pop( key, None ):
                self.bob.StopMovingEast( )
            
            return  # Do not continue
    
    def handleKeyPressedEvents( self, key ):
        
        if key == pygame.K_ESCAPE:
            self.keys_held = {} # Clear all held keys to prevent them from sticking
            self.Menu()
        
        if key == self.control_tech_tree:
            self.keys_held = {} # Clear all held keys to prevent them from sticking
            self.TechTree()
            
            return
        
        if key == pygame.K_1:
            self.bob.SwitchWeaponTo('Hands')
            
            return
        
        if key == pygame.K_2:
            self.bob.SwitchWeaponTo('Pistol')
            
            return
        
        if key == pygame.K_3:
            self.bob.SwitchWeaponTo('Rifle')
            
            return
        
        if key == pygame.K_4:
            self.bob.SwitchWeaponTo('Shotgun')
            
            return
        
        if key == self.control_shoot:
            if self.bob.CanFire():
                self.bob.FireWeapon()
                
                weapon = self.bob.current_weapon
                
                if weapon == 'Hands':
                    self.effects.append( BulletInfection(self.bob.current_x, self.bob.current_y, self, direction = self.bob.walk_in_directions or self.bob.last_direction, damage = -4 * (1 + self.bob.tech_levels['Human Killing Machine'] * 0.02) ) )
                    
                elif weapon == 'Pistol':
                    self.effects.append( BulletPistol(self.bob.current_x, self.bob.current_y, self, direction = self.bob.walk_in_directions or self.bob.last_direction, damage = -5 * (1 + self.bob.tech_levels['Human Killing Machine'] * 0.02) ) )
                    
                elif weapon == 'Rifle':
                    self.effects.append( BulletRifle(self.bob.current_x, self.bob.current_y, self, direction = self.bob.walk_in_directions or self.bob.last_direction, damage = -10 * (1 + self.bob.tech_levels['Human Killing Machine'] * 0.02) ) )
                    
                elif weapon == 'Shotgun':
                    self.effects.append( BulletShotgun(self.bob.current_x, self.bob.current_y, self, direction = self.bob.walk_in_directions or self.bob.last_direction, damage = -15 * (1 + self.bob.tech_levels['Human Killing Machine'] * 0.02) ) )
            
            return
        
        if key == self.control_use:
            
            item = self.GetCollidingWithItem()
            
            if item:
                item.HandleBeingPickedUp(self.bob)
                self.RemoveItem(item)
            
            return
        
        # If the player does not hit the boundary then move the world, boundaries, npcs, items and in game effects
        if key == self.control_move_south:
            self.keys_held.setdefault( key, 1)
            
            # If bob can move, then move the world around him
            if self.bob.MoveSouth( MOVEMENT_SPEED ):
                self.world.MoveSouth( MOVEMENT_SPEED )
                for npc in self.npcs:
                    # This is to perserve the walking animation
                    npc.ShiftSouth( MOVEMENT_SPEED )
                
                for effect in self.effects:
                    effect.MoveSouth( MOVEMENT_SPEED )
                
                for item in self.items:
                    item.MoveSouth( MOVEMENT_SPEED )
                
            return  # Do not continue
        
        if key == self.control_move_north:
            self.keys_held.setdefault( key, 1)
            
            # If bob can move, then move the world around him
            if self.bob.MoveNorth( MOVEMENT_SPEED ):
                self.world.MoveNorth( MOVEMENT_SPEED )
                for npc in self.npcs:
                    # This is to perserve the walking animation
                    npc.ShiftNorth( MOVEMENT_SPEED )
                
                for effect in self.effects:
                    effect.MoveNorth( MOVEMENT_SPEED )
                
                for item in self.items:
                    item.MoveNorth( MOVEMENT_SPEED )
            
            return  # Do not continue
        
        if key == self.control_move_west:
            self.keys_held.setdefault( key, 1)
            
            # If bob can move, then move the world around him
            if self.bob.MoveWest( MOVEMENT_SPEED ):
                self.world.MoveWest( MOVEMENT_SPEED )
                for npc in self.npcs:
                    # This is to perserve the walking animation
                    npc.ShiftWest( MOVEMENT_SPEED )
                
                for effect in self.effects:
                    effect.MoveWest( MOVEMENT_SPEED )
                
                for item in self.items:
                    item.MoveWest( MOVEMENT_SPEED )
                
            return  # Do not continue
        
        if key == self.control_move_east:
            self.keys_held.setdefault( key, 1)
            
            # If bob can move, then move the world around him
            if self.bob.MoveEast( MOVEMENT_SPEED ):
                self.world.MoveEast( MOVEMENT_SPEED )
                for npc in self.npcs:
                    # This is to perserve the walking animation
                    npc.ShiftEast( MOVEMENT_SPEED )
                
                for effect in self.effects:
                    effect.MoveEast( MOVEMENT_SPEED )
                
                for item in self.items:
                    item.MoveEast( MOVEMENT_SPEED )
            
            return  # Do not continue